// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// See License.txt in the project root for license information.
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading.Actors.Utilities;
using System.Threading;
using System.Threading.Actors;

namespace TestActorMethods
{
    [TestClass]
    public class AsyncQueueUnitTests
    {
        [TestMethod]
        public void AsyncQueue_Test()
        {
            AsyncQueue<int> queue = new AsyncQueue<int>();

            //
            // Test synchronous operation
            //

            // Add and remove items synchronously, and test CompleteAdding functionality
            for (int i = 0; i < 10; i++) queue.Enqueue(i);
            queue.CompleteAdding();
            Assert.AreEqual(queue.Count, 10, "Expected queue count to be 10 after 10 adds");
            for (int i = 0; i < 10; i++)
            {
                var item = queue.DequeueAsync().Result;
                Assert.AreEqual(item, i, "Items not coming out in the same order that they came in.");
            }
            Assert.AreEqual(queue.Count, 0, "Expected queue to be empty after removing all items");

            // Ensure that we get exceptions for Enqueuing after CompleteAdding(), or 
            // attempting to dequeue from an empty queue after CompleteAdding().
            Exception e = null;
            try { queue.Enqueue(42); } catch (Exception ex) {e = ex;}
            Assert.IsNotNull(e, "Expected exception on Enqueue after CompleteAdding");

            e = null;
            try { var item = queue.DequeueAsync().Result; } catch (Exception ex) { e = ex; }
            Assert.IsNotNull(e, "Expected exception on DequeueAsync after CompleteAdding");

            //
            // Test asynchronous consumption
            //

            queue = new AsyncQueue<int>();
            var ss_startRemoving = new SemaphoreSlim(0,1); // Initialize as unsignaled
            var mre_consumerDone = new ManualResetEventSlim(false);

            ConsumerLoop(queue, ss_startRemoving, mre_consumerDone); // fire and forget
            Assert.IsTrue(queue.Count == 0, "Expected count to be 0 before adding anything");
            for (int i = 0; i < 10; i++) queue.Enqueue(i);
            Assert.IsTrue(queue.Count == 10, "Expected count to be 10 after adding data, before enabling consumer");
            
            // Enable the consumer
            ss_startRemoving.Release();
            int totalMsWaited = 0;
            while (queue.Count != 0 && totalMsWaited < 2000)
            {
                Thread.Sleep(100);
                totalMsWaited += 100;
            }
            Assert.IsTrue(queue.Count == 0, "Expected consumer to empty queue within 2 seconds");
            Assert.IsFalse(mre_consumerDone.IsSet, "Didn't expect consumer to complete before CompleteAdding");

            // Make sure that CompleteAdding has its desired effect
            queue.CompleteAdding();
            mre_consumerDone.Wait(2000);
            Assert.IsTrue(mre_consumerDone.IsSet, "Expected consumer to complete within 2 seconds of CompleteAdding");
        }

        // Async loop to test asynchronous consumption
        private async void ConsumerLoop(
            AsyncQueue<int> queue, 
            SemaphoreSlim ss_startRemoving, 
            ManualResetEventSlim mre_consumerDone)
        {
            await ss_startRemoving.WaitAsync();
            while (true)
            {
                try
                {
                    var item = await queue.DequeueAsync();
                }
                catch (EndOfQueueException)
                {
                    break; // CompleteAdding must have been called
                }
            }
            mre_consumerDone.Set();
        }
    }
}
